/**
 * 
 */
package edu.umd.clip.lm.factors;

import java.util.*;
import java.io.*;
import java.util.regex.*;


/**
 * @author Denis Filimonov <den@cs.umd.edu>
 *
 */
public final class FactorTuple implements Cloneable, Serializable, Externalizable {
	static final long serialVersionUID = 1L;
	private static FactorTupleDescription description;
	protected static long overtMask;
	protected static long hiddenMask;
	private long bits = 0;

	private static final Pattern split_re = Pattern.compile(":");
	private static final long WORD_MASK = 0xffffffffffffffffL;
	
	/**
	 * @param description
	 * @param values
	 */
	protected FactorTuple(int[] values) {
		setValues(values); 
	}

	public FactorTuple() {
	}

	
	public FactorTuple(long bits) {
		this.bits = bits;
	}

	public FactorTuple(String valueString) {
		String strValues[] = split_re.split(valueString);
		if (description.numFactors() != strValues.length) {
			throw new Error("invalid valueString");
		}
		for(byte i=0; i<description.numFactors(); ++i) {
			setValue(i, Integer.parseInt(strValues[i]));
		}
	}
	
	public static long factorTuple(String valueString) {
		long bits = 0;
		String strValues[] = split_re.split(valueString);
		if (description.numFactors() != strValues.length) {
			throw new Error("invalid valueString");
		}
		for(byte i=0; i<description.numFactors(); ++i) {
			setValue(bits, i, Integer.parseInt(strValues[i]));
		}
		return bits;
	}
	
	public int[] getValues() {
		return getValues(bits);
	}

	public static int[] getValues(long bits) {
		final byte numFactors = description.numFactors();
		int[] values = new int[numFactors];
		for(byte i=0; i<numFactors; ++i) {
			values[i] = getValue(bits, i);
		}
		return values;
	}

	public void setBits(long bits) {
		this.bits = bits;
	}

	public long getBits() {
		return bits;
	}
	
	public void setValues(int[] values) {
		bits = setValues(bits, values);
	}
	
	public static long setValues(long bits, int[] values) {
		byte[] offsets = description.getFactorBitStart();
		byte[] lengths = description.getFactorBitLength();
		
		for(byte i=0; i<values.length; ++i) {
			long value = values[i];
			long mask = WORD_MASK >>> Long.SIZE - lengths[i]; 
			value &= mask;
			value <<= offsets[i];
			mask <<= offsets[i];
			bits &= ~mask;
			bits ^= value;			
		}
		return bits;
	}
	
	public int getValue(byte idx) {
		return getValue(bits, idx);
	}
	
	public static int getValue(long bits, byte idx) {
		byte offset = description.getFactorBitStart()[idx];
		byte length = description.getFactorBitLength()[idx];
		long mask = WORD_MASK >>> Long.SIZE - length;
		
		long val = (bits >>> offset) & mask;
		return (int)val;
	}
	
	public void setValue(byte idx, int value) {
		bits = setValue(bits, idx, value);
	}

	public static long setValue(long bits, byte idx, int value) {
		byte offset = description.getFactorBitStart()[idx];
		byte length = description.getFactorBitLength()[idx];
		long mask = WORD_MASK >>> Long.SIZE - length;
		long val = value;
		val &= mask;
		val <<= offset;
		mask <<= offset;
		bits &= ~mask;
		bits ^= val;
		return bits;
	}

	public int[] getOvertValues() {
		return getOvertValues(bits);
	}
	
	public static int[] getOvertValues(long bits) {
		byte[] offsets = description.getFactorBitStart();
		byte[] lengths = description.getFactorBitLength();

		byte[] overtFactors = description.getOvertFactors();
		int[] overtValues = new int[overtFactors.length];
		int i = 0;
		for(byte idx : overtFactors) {
			long mask = WORD_MASK >>> Long.SIZE - lengths[idx];
			long val = (bits >>> offsets[idx]) & mask;

			overtValues[i++] = (int)val;
		}
		return overtValues;
	}
	
	public void setOvertValues(int[] values) {
		bits = setOvertValues(bits, values);
	}
	
	public static long setOvertValues(long bits, int[] values) {
		byte[] offsets = description.getFactorBitStart();
		byte[] lengths = description.getFactorBitLength();

		byte[] overtFactors = description.getOvertFactors();
		assert(values.length == overtFactors.length);
		
		for(int i = 0; i < overtFactors.length; ++i) {
			byte idx = overtFactors[i];
			long value = values[i];
			long mask = WORD_MASK >>> Long.SIZE - lengths[idx]; 
			value &= mask;
			value <<= offsets[idx];
			mask <<= offsets[idx];
			bits &= ~mask;
			bits ^= value;
		}
		return bits;
	}
	
	public int[] getHiddenValues() {
		return getHiddenValues(bits);
	}
	
	public static int[] getHiddenValues(long bits) {
		byte[] offsets = description.getFactorBitStart();
		byte[] lengths = description.getFactorBitLength();

		byte[] hiddenFactors = description.getHiddenFactors();
		int[] hiddenValues = new int[hiddenFactors.length];
		int i = 0;
		for(byte idx : hiddenFactors) {
			long mask = WORD_MASK >>> Long.SIZE - lengths[idx];
			long val = (bits >>> offsets[idx]) & mask;

			hiddenValues[i++] = (int)val;
		}
		return hiddenValues;
	}
	
	public void setHiddenValues(int[] values) {
		bits = setHiddenValues(bits, values);
	}
	
	public static long setHiddenValues(long bits, int[] values) {
		byte[] offsets = description.getFactorBitStart();
		byte[] lengths = description.getFactorBitLength();

		byte[] hiddenFactors = description.getHiddenFactors();
		assert(values.length == hiddenFactors.length);
		
		for(int i = 0; i < hiddenFactors.length; ++i) {
			byte idx = hiddenFactors[i];
			long value = values[i];
			long mask = WORD_MASK >>> Long.SIZE - lengths[idx]; 
			value &= mask;
			value <<= offsets[idx];
			mask <<= offsets[idx];
			bits &= ~mask;
			bits ^= value;
		}
		return bits;
	}
	
	public static FactorTupleDescription getDescription() {
		return description;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return toString(bits);
	}

	public static String toString(long bits) {
		StringBuffer result = new StringBuffer();
		for(byte i=0; i<description.numFactors(); ++i) {
			if (i > 0) result.append(":");
			FactorDescription desc = description.getDescription(i);
			result.append(desc.getId());
			result.append("-");
			result.append(factorStringValue(bits, i));
		}
		return result.toString();
	}

	public String toStringNoNull() {
		return toStringNoNull(bits);
	}
	
	public static String toStringNoNull(long bits) {
		StringBuffer result = new StringBuffer();
		boolean hadSome = false;
		for(byte i=0; i<description.numFactors(); ++i) {
			int value = getValue(bits, i);
			if (Dictionary.isNull(value)) continue;
			if (hadSome) {
				result.append(":");
			}
			hadSome = true;
			FactorDescription desc = description.getDescription(i);
			result.append(desc.getId());
			result.append("-");
			result.append(factorStringValue(bits, i));
		}
		if (!hadSome) {
			return "<NULL>";
		}
		return result.toString();		
	}
	
	public String factorStringValue(byte idx) {
		return factorStringValue(bits, idx);
	}

	public static String factorStringValue(long bits, byte idx) {
		FactorDescription desc = description.getDescription(idx);
		String result;
		if (description.getParentIndex(idx) == -1) {
			result = desc.getDictionary().getWord(getValue(bits, idx));
		} else {
			result = desc.getDictionary(getValue(bits, description.getParentIndex(idx))).getWord(getValue(bits, idx));
		}
		return FLMInputParser.quote(result);
	}

	public String toValueString() {
		return toValueString(bits);
	}
	
	public static String toValueString(long bits) {
		StringBuffer sb = new StringBuffer(Integer.toString(getValue(bits, (byte) 0)));
		for(byte i=1; i<description.numFactors(); ++i) {
			sb.append(':');
			sb.append(FLMInputParser.quote(Integer.toString(getValue(bits, i))));
		}
		return sb.toString();
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#clone()
	 */
	public Object clone() {
		return new FactorTuple(bits);
	}

	@Override
	public int hashCode() {
		return hashCode(bits);
	}

	public static int hashCode(long bits) {
		final int prime = 31;
		int result = 1;
		result = prime * result + (int) (bits ^ (bits >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof FactorTuple))
			return false;
		FactorTuple other = (FactorTuple) obj;
		if (bits != other.bits)
			return false;
		return true;
	}

	
	private void writeObject(java.io.ObjectOutputStream out)
		throws IOException {
		out.writeLong(bits);
	}
	private void readObject(java.io.ObjectInputStream in)
		throws IOException, ClassNotFoundException {
		bits = in.readLong();
	}

	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
		bits = in.readLong();
	}

	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeLong(bits);
	}
	
	public boolean matches(FactorTuple tuple) {
		return matches(bits, tuple);
	}
	
	public static boolean matches(long bits, FactorTuple tuple) {
		return matches(bits, tuple.bits);
	}
	
	public static boolean matches(long bits, long tuple) {
		final int NULL = Dictionary.getNull();
		int[] tupleValues = getValues(tuple);
		for(byte i=0; i<description.numFactors(); ++i) {
			if (tupleValues[i] != NULL && tupleValues[i] != getValue(bits, i)) return false;
		}
		return true;
	}
	
	public byte[] nonNullFactors() {
		return nonNullFactors(bits);
	}
	
	public static byte[] nonNullFactors(long bits) {
		byte numFactors = description.numFactors();
		int NULL = Dictionary.getNull();
		byte nonNull[] = new byte[numFactors];
		byte idx = 0;
		for(byte i=0; i<numFactors; ++i) {
			if (getValue(bits, i) != NULL) {
				nonNull[idx++] = i;
			}
		}
		return Arrays.copyOf(nonNull, idx);
	}
	
	public byte[] nullFactors() {
		return nullFactors(bits);
	}

	public static byte[] nullFactors(long bits) {
		byte numFactors = description.numFactors();
		int NULL = Dictionary.getNull();
		byte Null[] = new byte[numFactors];
		byte idx = 0;
		for(byte i=0; i<numFactors; ++i) {
			if (getValue(bits, i) == NULL) {
				Null[idx++] = i;
			}
		}
		return Arrays.copyOf(Null, idx);
	}

	public static void setDescription(FactorTupleDescription description) {
		FactorTuple.description = description;
	}

	public static long getOvertMask() {
		return overtMask;
	}

	public static long getHiddenMask() {
		return hiddenMask;
	}
	
}
